using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;

using Microsoft.BizTalk.SSOClient.Interop;

namespace SSOConfig
{
    /// <summary>
    /// Helper class wrapping access to SSO store
    /// </summary>
    /// <seealso cref="http://blogs.msdn.com/kevin_michael_schultz/archive/2006/12/14/eat-my-rough-draft-punk.aspx"/>
    /// <seealso cref="http://suneetnangia.spaces.live.com/Blog/cns!AF14D545C10A3693!300.entry"/>
    public class SSOHelper
    {
        #region Private instance fields

        private ISSOAdmin _admin;
        private ISSOConfigStore _configStore;
        private ISSOMapper _mapper;

        #endregion

        #region Private instance properties

        private ISSOAdmin Admin
        {
            get
            {
                if (this._admin == null)
                {
                    this._admin = new ISSOAdmin();
                }
                return this._admin;
            }
        }

        private ISSOConfigStore ConfigStore
        {
            get
            {
                if (this._configStore == null)
                {
                    this._configStore = new ISSOConfigStore();
                }
                return this._configStore;
            }
        }

        private ISSOMapper Mapper
        {
            get
            {
                if (this._mapper == null)
                {
                    this._mapper = new ISSOMapper();
                }
                return this._mapper;
            }
        }

        #endregion

        #region Public instance methods

        /// <summary>
        /// Creates the given application in the SSO store
        /// </summary>
        /// <param name="app">Populated application</param>
        /// <param name="setFieldValues">Whether to save the application config values</param>
        public void CreateApplication(SSOApplication app, bool setFieldValues)
        {
            //delete the app before recreating:
            try
            {
                this.Admin.DeleteApplication(app.Name);
            }
            catch (COMException comEx)
            {
                this.HandleCOMException(comEx, SSOErrorCode.ApplicationNotExists);            
            }

            //create the app:
            int appFlags = 0;
            appFlags |= SSOFlag.SSO_FLAG_APP_CONFIG_STORE;
            appFlags |= SSOFlag.SSO_FLAG_SSO_WINDOWS_TO_EXTERNAL;
            appFlags |= SSOFlag.SSO_FLAG_APP_ALLOW_LOCAL;
            try
            {
                this.Admin.CreateApplication(app.Name, app.Description, app.ContactInfo, app.UserGroup, app.AdminGroup, appFlags, app.Fields.Length);
            }
            catch (COMException comEx)
            {
                this.HandleCOMException(comEx, 0);
            }

            //create fields:
            int blankFlag = 0;
            int maskFlag = SSOFlag.SSO_FLAG_FIELD_INFO_MASK;
            try
            {
                foreach (Field field in app.Fields)
                {
                    this.Admin.CreateFieldInfo(app.Name, field.Name, field.Masked ? maskFlag : blankFlag);
                }
            }
            catch (COMException comEx)
            {
                this.HandleCOMException(comEx, 0);
            }

            //enable app:
            this.Admin.UpdateApplication(app.Name, app.Description, app.ContactInfo, app.UserGroup, app.AdminGroup, SSOFlag.SSO_FLAG_ENABLED, SSOFlag.SSO_FLAG_ENABLED);

            //set field values:
            if (setFieldValues)
            {
                this.UpdateFieldValues(app);
            }
        }

        /// <summary>
        /// Updates the values in the SSO store for the given application
        /// </summary>
        /// <param name="app">Populated application</param>
        public void UpdateFieldValues(SSOApplication app)
        {
            //ensure the field values are valid:
            app.Validate();

            //map field values to property bag:
            SSOPropertyBag propertyBag = new SSOPropertyBag();
            foreach (Field field in app.Fields)
            {
                propertyBag.Setvalue(field.Name, field.ValueObject);
            }

            //write the properties to config store:
            try
            {
                this.ConfigStore.SetConfigInfo(app.Name, app.Name, propertyBag);
            }
            catch (COMException comEx)
            {
                this.HandleCOMException(comEx, 0);
            }
        }

        /// <summary>
        /// Retrieves an application from the SSO store
        /// </summary>
        /// <param name="applicationName">Application name to retrieve</param>
        /// <returns>Populated application</returns>
        public SSOApplication GetApplication(string applicationName)
        {
            SSOApplication app = new SSOApplication();
            app.Name = applicationName;

            //get the app info:
            string description = null;
            string contactInfo = null;
            string userGroup = null;
            string adminGroup = null;
            int flags;
            int fieldCount;

            try
            {
                this.Admin.GetApplicationInfo(applicationName, out description, out contactInfo, out userGroup, out adminGroup, out flags, out fieldCount);
            }
            catch (COMException comEx)
            {
                this.HandleCOMException(comEx, 0);
            }

            //map out fields to app:
            app.Description = description;
            app.ContactInfo = contactInfo;
            app.UserGroup = userGroup;
            app.AdminGroup = adminGroup;

            //get the config values:
            app.Fields = this.GetFieldValues(applicationName).ToArray();
            return app;
        }

        /// <summary>
        /// Returns whether the named application exists in the SSO store
        /// </summary>
        /// <param name="applicationName">Application name</param>
        /// <returns>Whether the application exists</returns>
        public bool ApplicationExists(string applicationName)
        {
            List<string> apps = this.GetApplications();
            return apps.Contains(applicationName);
        }

        /// <summary>
        /// Returns a list of applications in the SSO store
        /// </summary>
        /// <returns>Application names</returns>
        public List<string> GetApplications()
        {
            //get the app list:
            string[] applications = null;
            string[] descs;
            string[] contacts;

            try
            {
                this.Mapper.GetApplications(out applications, out descs, out contacts);
            }
            catch (COMException comEx)
            {
                this.HandleCOMException(comEx, 0);
            }

            List<string> apps = new List<string>(applications.Length);
            apps.AddRange(applications);

            return apps;
        }

        /// <summary>
        /// Deletes the named application from the config store
        /// </summary>
        /// <param name="applicationName"></param>
        public void DeleteApplication(string applicationName)
        {
            try
            {
                this.Admin.DeleteApplication(applicationName);
            }
            catch (COMException comEx)
            {
                this.HandleCOMException(comEx, SSOErrorCode.ApplicationNotExists);
            }
        }

        #endregion

        #region Private instance methods

        private List<Field> GetFieldValues(string applicationName)
        {
            //load values - we need to flag runtime to get back any masked values (note - all values are
            //encrypted in SSO, so is Masked useful?)
            SSOPropertyBag propertyBag = new SSOPropertyBag();
            int runtimeFlag = SSOFlag.SSO_FLAG_RUNTIME;

            try
            {
                this.ConfigStore.GetConfigInfo(applicationName, applicationName, runtimeFlag, propertyBag);
            }
            catch (COMException comEx)
            {
                this.HandleCOMException(comEx, 0);
            }

            //whether the field is masked comes from a separate call:
            string[] fieldNames = null;
            int[] fieldFlags = null;

            try
            {
                this.Mapper.GetFieldInfo(applicationName, out fieldNames, out fieldFlags);
            }
            catch (COMException comEx)
            {
                this.HandleCOMException(comEx, 0);
            }

            List<string> maskedFields = new List<string>(fieldNames.Length);
            for (int i = 0; i < fieldNames.Length; i++)
            {
                if ((fieldFlags[i] & SSOFlag.SSO_FLAG_FIELD_INFO_MASK) == SSOFlag.SSO_FLAG_FIELD_INFO_MASK)
                {
                    maskedFields.Add(fieldNames[i]);
                }
            }

            //map field values from property bag:
            List<Field> fields = new List<Field>();
            Field field = null;
            foreach (string key in propertyBag.Keys)
            {
                field = new Field();
                field.Name = key;
                field.ValueObject = propertyBag.GetValue(field.Name);
                field.Type = propertyBag.GetValue(field.Name).GetType().FullName;
                field.Masked = maskedFields.Contains(field.Name);
                fields.Add(field);
            }

            return fields;
        }

        private void HandleCOMException(COMException comEx, int ignoreErrorCode)
        {
            //if this error code is OK, ignore it:
            if (comEx.ErrorCode != ignoreErrorCode)
            {
                throw new ApplicationException(string.Format("SSO error - code: {0}, message: {1}", comEx.ErrorCode, comEx.Message), comEx);

            }
        }

        #endregion

        #region Private structs

        private struct SSOErrorCode
        {
            public const int ApplicationExists = -1073731072;
            public const int ApplicationNotExists = -1073731068;
        }

        #endregion
    }
}